#ifndef __TCP_ECHO_SERVER_HPP__
#define __TCP_ECHO_SERVER_HPP__

#include"Logger.hpp"
#include"Comm.hpp"
#include"InetAddr.hpp"
#include"ThreadPool.hpp"

#include<iostream>
#include<functional>
#include<pthread.h>
#include<sys/wait.h>
#include<string>
#include<memory>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

static const uint16_t gdefaultport = 8080;
static const int gbacklog = 8;

using task_t = std::function<void()>;

class TcpEchoServer
{
private:
    void HandlerIO(int sockfd , InetAddr client)
    {
        //负责IO
        while(true)
        {
            char buffer[1024];
            buffer[0]=0;
            //read , 返回读取的个数；在网络通信中，返回值为0，代表客户端退出，为-1代表读错误
            int n =read(sockfd , buffer , sizeof(buffer)-1);//因为读取的是字符串，确保最后一个为\0，有位置可以放
            if(n>0)
            {
                //读成功，手动添加 \0
                buffer[n]=0;
                //处理一下字符串给服务器写回
                std::string echo_string = "server echo#";
                echo_string += buffer;

                //可以debug 一下
                LOG(LogLevel::DEBUG) << client.ToString() <<"say: " << buffer;
                write(sockfd , echo_string.c_str() , echo_string.size());
            }
            else if(n==0)
            {
                LOG(LogLevel::INFO) << "client 退出， 服务端也退出: " << sockfd;
                break;
            }
            else
            {
                LOG(LogLevel::WARNING) << "读: " << client.ToString() <<" 错误, sockfd:" << sockfd;
                break;
            }
        }
        //只要退出循环均需要关闭文件
        close(sockfd);
    }
public:
    TcpEchoServer(uint16_t port = gdefaultport)
    :_port(port)
    {}
    ~TcpEchoServer(){}

    //初始化、启动
    //初始化：创建sockfd、bind、监听listen
    //启动：（服务器是一个死循环） 连接accept 、IO:read、write

    void Init()
    {
        //1、创建套接字
        _listensockfd = socket(AF_INET,SOCK_STREAM , 0);//tcp->面向字节流； udp->面向数据报
        //返回值为文件描述符
        if(_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "创建套接字失败";
            exit(SOCKET_CREATE_ERR);
        }
        LOG(LogLevel::INFO) << "创建套接字成功" << _listensockfd;

        //将sockfd 绑定ip 和端口
        // struct sockaddr_in local;
        // memset(&local, 0 ,sizeof(local));
        // local.sin_family = AF_INET;
        // //注意主机转网络
        // local.sin_port = htons(_port);
        // local.sin_addr.s_addr = htonl(INADDR_ANY);
        InetAddr local(_port);

        //2、绑定
        // int n = bind(_listensockfd , (struct sockaddr*)&local , sizeof(local));
        int n = bind(_listensockfd , local.Addr() , local.Length());
        if(n < 0)
        {
            LOG(LogLevel::FATAL) << "绑定套接字失败";
            exit(SOCKET_BIND_ERR);
        }
        LOG(LogLevel::INFO) << "绑定套接字成功";

        //3、监听
        //listen -> tcp 面向连接，即tcp 的服务器需要随时等待客户端来连接，即tcp 服务器需要随时处于监听状态
        //listen 的返回值，成功返回0，失败返回-1； 其第二个参数代表着底层全连接队列长度-1，一般设置为8、16……
        int m = listen(_listensockfd , gbacklog);
        if(m < 0)
        {
            LOG(LogLevel::FATAL) << "监听套接字失败";
            exit(SOCKET_LISTEN_ERR);
        }

        LOG(LogLevel::FATAL) << "监听套接字成功";
    }

    class ThreadData
    {
    public:
        ThreadData(int sockfd , const InetAddr &addr , TcpEchoServer* self)
        :_sockfd(sockfd)
        ,_addr(addr)
        ,_self(self)
        {}
        //HandlerIO需要 sockfd , clientaddr
        int _sockfd;
        InetAddr _addr;
        //以及含有HandlerIO方法,HandlerIO属于 TcpEchoServer 的成员方法，只需要有个 TcpEchoServer 对象就可以了
        TcpEchoServer* _self;
    };

    static void *Routine(void* args)
    {
        ThreadData* td = static_cast<ThreadData*>(args);
        //将线程设置为线程分离
        pthread_detach(pthread_self());
        //调用HandlerIO
        td->_self->HandlerIO(td->_sockfd ,td->_addr);

        return (void*)0;
    }

    //启动tcp 服务器
    //处理连接、IO
    void Start()
    {
        while(true)
        {
            //int accept(int sockfd, struct sockaddr *_Nullable restrict addr,
            //socklen_t *_Nullable restrict addrlen);
            //accept 的第一个参数为 _listensockfd ; 在获取连接的同时还需要知道是谁发的
            //第二个参数为输出型参数，第三个参数为输入、输出型参数;返回值：成功返回文件描述符，失败返回-1；
            //之所以返回文件描述符是因为 _listensockfd 只负责获取连接，而accept 返回的文件描述符负责进行通信
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensockfd , (struct sockaddr*)&peer , &len);
            if(sockfd < 0)
            {
                //服务器获取连接错误，并不能引起服务器的错误，这属于常规可能的情况，所以此处不用exit
                // 仅需打印日志+ continue 便可
                LOG(LogLevel::WARNING) << "连接客户端错误" ;
                continue;
            }
            InetAddr clientaddr(peer);
            LOG(LogLevel::INFO) << "连接客户端成功 , sockfd : " << sockfd << 
                " client addr: " << clientaddr.ToString();
            ///走到此处就是连接成功
            //处理IO
            // HandlerIO(sockfd , clientaddr);
            //使用多进程
            // pid_t id = fork();
            // if(id < 0)
            // {
            //     LOG(LogLevel::FATAL) << "创建子进程失败" ;
            //     exit(FORK_ERR);
            // }
            // else if(id == 0)
            // {
                    //关闭不适用的fd
                    //close(_listensockfd);
            //     if(fork()) exit(OK);
            //     HandlerIO(sockfd,clientaddr);
            //     exit(OK);
            // }
            // else{
            //     //父进程需要等待回收子进程
                    //close(sockfd);
            //     pid_t rid = waitpid(id , nullptr , 0);//父进程阻塞式等待子进程，同样也还是串行
            //     //即便是改成非阻塞式等待，效果也不会很好；
            //     //此处还有一种解决方案：让子进程创建孙子进程，然后释放子进程，那么孙子进程便会变成孤儿进程，由bash 来回收
            //     //
            //     if(rid<0)
            //     {
            //         LOG(LogLevel::WARNING) << "等待子进程失败";
            //         return;
            //     }
            // }

            //另外还可以使用多线程版本
            //因为成员函数会多一个 this 指针，所以Routine 函数必须为静态成员函数，而静态成员函数不能直接访问非静态成员方法、属性
            //调用HandlerIO所需要的参数利用给线程传参来解决，我们自己封装一个类
            ThreadData* td = new ThreadData(sockfd , clientaddr , this);
            pthread_t tid;
            pthread_create(&tid , nullptr , Routine , (void*)td);

            //还可以使用线程池，将HandlerIO处理成任务放入线程池的任务队列之中
            // ThreadPool<task_t>::GetInstance()->Enqueue([this , sockfd , clientaddr](){
            //     this->HandlerIO(sockfd , clientaddr);
            // });
        }
    }

private:
    int _listensockfd;
    uint16_t _port; // 关于 struct sockaddr_in 的创建，可以使用我们封装的类
};

#endif